#include <stdio.h>
#include <string.h>

#include <lua.hpp>

#if defined(_WIN32) || defined(_WIN64)
#define strncasecmp _strnicmp
#endif

#include "gip.h"

/*
static int freeImage( lua_State* L) {
    void* image = lua_touserdata( L, lua_upvalueindex( 1));
    gip_freeImage( image);

    printf( "success free image.\n");
    return 0;
}*/

static int freeImage( lua_State* L) {
    void* image = *(void**)lua_touserdata( L, 1);
    gip_freeImage( image);

    //printf( "success free image.\n");
    return 0;
}


static int strToFormat( const char* strFormat) {
    if ( !strFormat)
        return gip_PNG;

    if ( strncasecmp( strFormat, "bmp", 3) == 0)
        return gip_BMP;
    else if ( strncasecmp( strFormat, "dds", 3) == 0)
        return gip_DDS;
    else if ( strncasecmp( strFormat, "jpg", 3) == 0)
        return gip_JPG;
    else if ( strncasecmp( strFormat, "tga", 3) == 0)
        return gip_TGA;
    else if ( strncasecmp( strFormat, "jai", 3) == 0)
        return gip_JAI;
    else
        return gip_PNG;
}

static int saveToFile( lua_State* L) {
    if ( lua_gettop( L) < 1)
        return 0;
    if ( !lua_isstring( L, 1))
        return 0;
    const char* filename = lua_tostring( L, 1);
    if ( !filename)
        return 0;
    
    void* image = lua_touserdata( L, lua_upvalueindex( 1));
    if ( !image)
        return 0;

    const char* strFormat = NULL;
    if ( lua_gettop( L) >= 2 && lua_isstring( L, 2))
        strFormat = lua_tostring( L, 2);
    else {
        size_t len = strlen( filename);
        if ( len > 4) {
            if ( filename[len - 4] == '.')
                strFormat = filename + len - 3;
        }
    }

    int format = gip_PNG;
    format = strToFormat( strFormat);

    if ( int ret = gip_saveToFile( image, format, filename)) {
        lua_pushinteger( L, ret);
        return 1;
    }
    
    lua_pushboolean( L, 1);
    return 1;
}

static int saveToMemory( lua_State* L) {
    int format = gip_PNG;
    if ( lua_gettop( L) >= 1 && lua_isstring( L, 1)) {
        const char* strFormat = lua_tostring( L, 1);
        format = strToFormat( strFormat);
    }

    void* image = lua_touserdata( L, lua_upvalueindex( 1));
    if ( !image) {
        return 0;
    }

    void* data;
    size_t size;
    void* extra;
    if ( int ret = gip_saveToMemory( image, format, &data, &size, &extra)) {
        return 0;
    }
    lua_pushlstring( L, (const char*)data, size);
    gip_freeMemory( data, extra);
    return 1;
}

static int getImageW( lua_State* L){
    void* image = lua_touserdata( L, lua_upvalueindex( 1));
    if ( !image)
        return 0;
    int w = gip_getImageW( image);
    lua_pushinteger( L, w);
    return 1;
}

static int getImageH( lua_State* L){
    void* image = lua_touserdata( L, lua_upvalueindex( 1));
    if ( !image)
        return 0;
    int h = gip_getImageH( image);
    lua_pushinteger( L, h);
    return 1;
}

static int validArea( lua_State* L) {
    void* image = lua_touserdata( L, lua_upvalueindex( 1));
    if ( !image)
        return 0;
    int x, y, w, h;
    if ( int r = gip_validArea( image, &x, &y, &w, &h)) {
        printf( "valid area failed %d\n", r);
        return 0;
    }
    lua_pushinteger( L, x);
    lua_pushinteger( L, y);
    lua_pushinteger( L, w);
    lua_pushinteger( L, h);
    return 4;
}

#define PUSH_IMAGE_FUNC( L, image, name, func, tabeIndex) \
    lua_pushliteral( L, name); \
    lua_pushlightuserdata( L, image); \
    lua_pushcclosure( L, func, 1); \
    lua_settable( L, tableIndex);
    
static int createImageTable( lua_State* L, void* image);

int cutImage( lua_State* L) {
    void* image = lua_touserdata( L, lua_upvalueindex( 1));
    if ( !image)
        return 0;
    
    if ( lua_gettop( L) < 4)
        return 0;
    int x = lua_tointeger( L, 1);
    int y = lua_tointeger( L, 2);
    int w = lua_tointeger( L, 3);
    int h = lua_tointeger( L, 4);

    void* newImage = gip_cutImage( image, x, y, w, h);
    if ( !newImage)
        return 0;

    return createImageTable( L, newImage);
}

static int createImageTable( lua_State* L, void* image) {
    lua_newtable( L);
    int tableIndex = lua_gettop( L);
    
    PUSH_IMAGE_FUNC( L, image, "save", saveToFile, tableIndex);
    PUSH_IMAGE_FUNC( L, image, "saveToMemory", saveToMemory, tableIndex);
    PUSH_IMAGE_FUNC( L, image, "getW", getImageW, tableIndex);
    PUSH_IMAGE_FUNC( L, image, "getH", getImageH, tableIndex);
    PUSH_IMAGE_FUNC( L, image, "validArea", validArea, tableIndex);
    PUSH_IMAGE_FUNC( L, image, "cut", cutImage, tableIndex);
    
    lua_pushliteral( L, "_image");    
    void** pimage = (void**)lua_newuserdata( L, sizeof( void**));
    *pimage = image;    
    lua_newtable( L);
    lua_pushliteral( L, "__gc");
    lua_pushcclosure( L, freeImage, 0);
    lua_settable( L, -3);
    lua_setmetatable( L, -2);
    
    
    lua_settable( L, tableIndex);

    
    return 1;
}


static int loadFromFile( lua_State* L) {
    if ( lua_gettop( L) < 1)
        return 0;
    if ( !lua_isstring( L, 1))
        return 0;
    const char* filename = lua_tostring( L, 1);
    if ( !filename)
        return 0;
        
    lua_newtable( L);
    int tableIndex = lua_gettop( L);

    void* image;
    if ( gip_loadImageFromFile( filename, &image))
        return 0;

    return createImageTable( L, image);
}

static int loadFromMemory( lua_State* L) {
    if ( lua_gettop( L) < 1)
        return 0;
    if ( !lua_isstring( L, 1))
        return 0;
    size_t size;
    const void* data = lua_tolstring( L, 1, &size);
    if ( !data)
        return 0;

    void* image;
    if ( gip_loadImageFromMemory( data, size, &image))
        return 0;

    return createImageTable( L, image);
}

static int init( lua_State* L) {
    if ( gip_init()) {
        lua_pushboolean( L, 0);
        return 1;
    }
    else {
        lua_pushboolean( L, 1);
        return 1;
    }
}

static int deInit( lua_State* L) {
    gip_deInit();
    return 0;
}

static void _transFunc( const char* filename, int isFile, void* data) {
    lua_State* L = (lua_State*)data;
    int top = lua_gettop( L);
    lua_pushvalue( L, top);
    lua_pushstring( L, filename);
    lua_pushboolean( L, isFile);
    lua_pcall( L, 2, 0, 0);
    lua_settop( L, top);
}

static int transDir( lua_State* L) {
    if ( lua_gettop( L ) < 2) {
        lua_pushboolean( L, 0);
        return 1;
    }
    if ( !lua_isstring( L, 1)) {
        lua_pushboolean( L, 0);
        return 1;
    }
    const char* path = lua_tostring( L, 1);
    
    if ( !lua_isfunction( L, 2)) {
        lua_pushboolean( L, 0);
        return 0;
    }
    
    int transSub = 1;
    if ( lua_gettop( L) >= 3) {
        transSub = lua_toboolean( L, 3) ? 1 : 0;
        lua_settop( L, 2);
    }

    if ( gip_transDir( path, _transFunc, L, transSub)) {
        lua_pushboolean( L, 0);
        return 0;
    }

    lua_pushboolean( L, 1);
    return 1;
}

static int createParentDir( lua_State* L) {
    if ( lua_gettop( L ) < 1) {
        lua_pushboolean( L, 0);
        return 1;
    }
    if ( !lua_isstring( L, 1)) {
        lua_pushboolean( L, 0);
        return 1;
    }
    const char* filename = lua_tostring( L, 1);
    int ret = gip_createParentDir( filename);
    lua_pushinteger( L, ret);
    return 1;
}

static int setJpgQuality( lua_State* L) {
    int percent = 75;
    if ( lua_gettop( L) >= 1) {
        if ( lua_isnumber( L, 1))
            percent = lua_tonumber( L, 1);
    }
    gip_setJpgSaveQuality( percent);
    return 0;        
}

#ifdef _WIN32
#include <windows.h>
static LARGE_INTEGER _tstart, _tend;
static LARGE_INTEGER freq;
static int tstart( lua_State* L)
{
    static int first = 1;
    if(first) {
        QueryPerformanceFrequency(&freq);
        first = 0;
    }
    QueryPerformanceCounter(&_tstart);
    return 0;
}
static void tend(void)
{
    QueryPerformanceCounter(&_tend);
}
static double tval()
{
    return ((double)_tend.QuadPart -
                (double)_tstart.QuadPart)/((double)freq.QuadPart / 1000.0);
}
#else
#include <sys/utsname.h>
static struct timeval _tstart, _tend;
static struct timezone tz;
static int tstart( lua_State* L)
{
    gettimeofday(&_tstart, &tz);
    return 0;
}
static void tend(void)
{
    gettimeofday(&_tend,&tz);
}
static double tval()
{
    double t1, t2;
    t1 =  (double)_tstart.tv_sec + (double)_tstart.tv_usec/(*1000);
    t2 =  (double)_tend.tv_sec + (double)_tend.tv_usec/(*1000);
    return t2-t1;
}
#endif
static int timeEnd( lua_State* L) {
    tend();
    lua_pushnumber( L, tval());
    return 1;
}

static const struct luaL_reg libName[] =
{
    {"init", init},
    {"deInit", deInit},
    {"load", loadFromFile},
    {"loadFromMemory", loadFromMemory},
    {"setJpgQuality", setJpgQuality},
    {"transDir", transDir},
    {"createParentDir", createParentDir},
    {"timeStart", tstart},
    {"timeEnd", timeEnd},
    {NULL, NULL},
};

extern "C"
#if defined(_WIN32) || defined(_WIN64)
__declspec( dllexport)
#endif
int luaopen_gip( lua_State* L) {
    luaL_register( L, "gip", libName);
    return 1;
}
